classdef Topology < handle
    
   properties (SetAccess = 'public', GetAccess = 'public')
       number_of_hap_nodes;
       number_of_gap_nodes;
       total_num_of_nodes;
       nodes;
       % costs_matrix(i,j)=inf if no link between node i and node j, and k if the cose of edge(i,k)=k
       costs_matrix;
       maximum_link_length_allowed;
       %CR - this should not be here?
       router;
   end
   
   methods(Static)
       function topology = read_topology_from_file(file_name, router_type)
           param = load(file_name,'-mat','topology');
           topology = param.topology;
           topology.reinit_topology_from_file(router_type);
       end
   end
   
   methods
       
       function create_node_adapters(obj)
            for i = 1:(obj.total_num_of_nodes)
               %create link rate generator and adapter
               curr_node = obj.nodes(i);
               curr_node.neighbours = obj.get_neighbours(i);
               link_rate_generator = UniformLinkRateGenerator(length(curr_node.neighbours));
               curr_node.node_adapter = WirelessAdapter(link_rate_generator, curr_node);
           end
       end
       
       function obj = Topology(number_of_hap_nodes,  maximum_link_length_allowed, maximum_link_length_allowed_to_gaps, use_romer)
           if nargin == 0
               return;
           end
           
           obj.number_of_hap_nodes = number_of_hap_nodes;
           
           % set nodes:
           rand('state', sum(100*clock)); % sets random seed
           x_values = rand(1, number_of_hap_nodes) * 1000;
           y_values = rand(1, number_of_hap_nodes) * 1000;
           
           %create router
           if use_romer
               obj.router = RomerRouter(obj);
           else
               obj.router = DijkstraRouter(obj);
           end
           
           %create nodes with distances
           for i=1:obj.number_of_hap_nodes
               obj.nodes = [obj.nodes Node(i,  x_values(i), y_values(i), obj.router, true)];
           end
           
           obj.init_gap_nodes();
           obj.total_num_of_nodes = obj.number_of_hap_nodes + obj.number_of_gap_nodes;
           
           % CR - change cost (not pyshical distances)
           % set costs matrix:
            obj.create_cost_matrix(maximum_link_length_allowed, maximum_link_length_allowed_to_gaps);
           
           % create adapters for all nodes
          obj.create_node_adapters();

           % set distance vector to all nodes
           for start_node = 1:(obj.number_of_hap_nodes + obj.number_of_gap_nodes)
               distance_vector = dijkstra(obj.costs_matrix, start_node);
               obj.nodes(start_node).distance_vector = distance_vector';
           end
       end
       
       function create_cost_matrix(obj,maximum_link_length_allowed, maximum_link_length_allowed_to_gaps)
           obj.costs_matrix = inf * ones(obj.total_num_of_nodes, obj.total_num_of_nodes);
               for i = 1:obj.total_num_of_nodes
                   for j = 1:obj.total_num_of_nodes
                       link_length = sqrt((obj.nodes(i).x_value - obj.nodes(j).x_value)^2 + (obj.nodes(i).y_value - obj.nodes(j).y_value)^2);
                       
                       if (i > obj.number_of_hap_nodes && j > obj.number_of_hap_nodes)
                           % gap-gap link
                           continue;
                       end
                       if (i <= obj.number_of_hap_nodes && j > obj.number_of_hap_nodes)
                            % hap-gap link
                           if link_length <= maximum_link_length_allowed_to_gaps   
                               obj.costs_matrix(i, j) = link_length;
                           end
                       else
                            % hap-hap link
                           if link_length <= maximum_link_length_allowed
                               obj.costs_matrix(i, j) = link_length;
                           end
                       end
                   end
               end
       end
       
       function neighbours_vector = get_neighbours(obj, node_id)
           neighbours_vector = [];
           for i = 1:(obj.number_of_hap_nodes + obj.number_of_gap_nodes)
               if (obj.costs_matrix(node_id, i)~= inf) && (node_id ~= i)
                   neighbours_vector = [ neighbours_vector obj.nodes(i)];
               end
           end
       end
       
       % check if the topology has no pending packets
       function ret = are_all_nodes_idle(obj)
           ret = true;
           for i=1:obj.number_of_hap_nodes
               curr_node = obj.nodes(i);
               if ~curr_node.is_node_idle()
                   ret = false;
                   return;
               end
           end
       end
       
       %arab func
       function init_gap_nodes(obj)
           if (obj.number_of_hap_nodes < 50)
               x_values = [333 333 666 666];
               y_values = [333 666 333 666];
           elseif (obj.number_of_hap_nodes < 100)
               x_values = [250 250 500 500 750 750];
               y_values = [333 666 333 666 333 666];
           else
               x_values = [250 250 250 500 500 500 750 750 750];
               y_values = [250 500 750 250 500 750 250 500 750];
           end
           
           obj.number_of_gap_nodes = length(x_values);
           
           %create nodes with distances
           for i=1:obj.number_of_gap_nodes
               obj.nodes = [obj.nodes Node(i + obj.number_of_hap_nodes,  x_values(i), y_values(i), obj.router, false)];
           end
           
       end
       
       function init_nodes_and_adapters(obj)
           obj.router = [];
           old_nodes = obj.nodes;
           new_vec(1,length(old_nodes)) = Node();
           obj.nodes = new_vec;
           
           %init node
           for i=1:length(old_nodes)
               old_node =old_nodes(i);
               new_node = Node(old_node.id, old_node.x_value, old_node.y_value, [], old_node.is_hap);
               new_node.distance_vector = old_node.distance_vector;
               obj.nodes(i) = new_node;
           end
           
           obj.create_node_adapters();
       end
       
       function reinit_topology_from_file(obj, router_type)
           params = ParamsSingleton.instance();
           obj.init_nodes_and_adapters();
           if router_type==params.router_type_ROMER
               obj.set_romer_router();
           elseif router_type==params.router_type_DIJKSTRA
               obj.set_dijkstra_router();
           end
           global_depositor = GlobalPacketDepositor.instance();
           global_depositor.set_nodes(obj.nodes);
           statistics_manager = StatisticsManager.instance();
           statistics_manager.set_topology(obj);
       end
       
       function set_dijkstra_router(obj)
           obj.router = DijkstraRouter(obj);
           for node_id=1:length(obj.nodes)
               obj.nodes(node_id).node_router = obj.router;
           end
       end
       
       function set_romer_router(obj)
           obj.router = RomerRouter(obj);
           for node_id=1:length(obj.nodes)
               obj.nodes(node_id).node_router = obj.router;
           end
       end
       
       function save_topology_to_file(obj, pathname, filename)
           topology = obj;
           save(fullfile(pathname, filename), 'topology', '-mat');
       end
       
   end
   
end